'use strict';

//need url, $scope, $scope.items, $scope.skip
app.factory("Paging", ["$q", "$http", "$rootScope", "$token", "$timeout", "$canceler", "HashList", function ($q, $http, $rootScope, $token, $timeout, $canceler, HashList) {

    function Paging(options) {
        this.store = options.store;
        this.requestOptions = options.request;
        this.removeOptions = options.remove;
        this.singleAppOptions = options.singleApp;
        if(!this.singleAppOptions) throw new Error("singleApp is empty.", options);
        if(!this.singleAppOptions.type) throw new Error("singleApp.type is empty.");
        if(!this.singleAppOptions.options) throw new Error("singleApp.options is empty.");
        this.cacheSize = 2;
        this.items = new HashList(options);
        this.skip = 0;
        this.isSynchronized = false;
        this.isReady = false;

        var self = this;
    };

    Paging.prototype.init = function () {
        var self = this;
        return $timeout(function () {
            //console.log(self.__proto__.constructor.name, "init", self.isReady);
            if (self.isSynchronized) return;
            return self.get();
        });
    };

    Paging.prototype.refresh = function () {
        var self = this;
        return $timeout(function () {
            console.log(self.__proto__.constructor.name, "refresh");
            return self.store.clear().then(function () {
                self.items.clear();
                self.skip = 0;
                self.isReady = self.isSynchronized = false;
                //return self.get();
            });
        });
    };

    Paging.prototype.paging = function () {
        //console.log(this.__proto__.constructor.name, "paging");
        return this.append(this.skip);
    };

    Paging.prototype.get = function () {
        var self = this;
        console.log(self.__proto__.constructor.name, "get");
        self.skip = 0;

        return $timeout(function () {
            if (self.isReady) {
                console.log(self.__proto__.constructor.name, "already ready!");
                return;
            }
            if (!self.store) {
                self.isReady = true;
                return;
            };
            return self.store.getAll().then(function (data) { //load data from cache
                console.log(self.__proto__.constructor.name, "clear items before read cache.");
                self.items.clear();
                data.forEach(function(item){
                    var object = self.createProvider(item);
                    //console.log(self.__proto__.constructor.name, "insert item (from cache)", object.data._id, object.data.articles[0].content);
                    self.items.insert(object);
                });
                console.log(self.__proto__.constructor.name, "is ready (initialize from cache).");
                self.isReady = true;
            });
        }).then(function () { //load data from database
            return self.append(0).then(function () {

                self.isSynchronized = true;
                console.log(self.__proto__.constructor.name, "is synchronize (synchronize from http) skip:", self.skip);
                //$timeout(function(){ 
                    self.saveToStore();
                //});
            });
        });
    };

    Paging.prototype.saveToStore = function () {
        var self = this;
        if (!self.store) return;

        var dataToStore = self.items.flatten().reduce(function(previous, current){
            previous.push(current.data);
            return previous;
        }, []);

        return self.store.saveAll(dataToStore);
    };

    Paging.prototype.append = function (skip) {
        var self = this;
        var originalSkip = skip;
        
        console.log(self.__proto__.constructor.name, "append (skip:", skip, ")");

        return $token.get().then(function (token) {

            var url = self.requestOptions.url;

            return $http.get(url, { params: { token: token, skip: skip, limit: 1 }, timeout: $canceler.create().promise }).then(function (res) {
                //console.log("http skip", skip, res.data);
                return res.data;
            });
        }).then(function (data) {
            
            console.log(self.__proto__.constructor.name, "append => http done (skip:", skip, ")");

            if (skip == 0) {
                console.log(self.__proto__.constructor.name, "clear items (first append)");
                self.items.clear();
            }
            data.forEach(function(item){
                var object = self.createProvider(item);
                //console.log(self.__proto__.constructor.name, "insert item", object.data._id, object.data.articles[0].content);
                self.items.insert(object);
            });

            skip = skip + data.length;

            if (skip < self.cacheSize && originalSkip < skip)
                return self.append(skip);

            var old = self.skip;
            self.skip = skip;

            console.log(self.__proto__.constructor.name, "old skip", old ,"new skip", self.skip);
        });
    };

    Paging.prototype.createProvider = function (data) {  
        var Instance = this.singleAppOptions.type;
        var options = angular.copy(this.singleAppOptions.options);
        options.data = data;
        if(options.request) 
            options.request.url += "/" + data._id; /* override url */
        return new Instance(options);
    };

    Paging.prototype.remove = function (item) {
        var self = this;
        return $token.get().then(function (token) {
            if (!self.removeOptions) throw new Error("Empty configuration for remove options.");

            var req = {};
            var id = $rootScope.$eval(self.removeOptions.request.value, item)
            req[self.removeOption.request.key] = id;

            return $http.post(self.removeOption.url, req, { params: { token: token }, timeout: $canceler.create().promise }).then(function () {
                return self.store.remove(id).then(function () {
                    self.items.remove(item);
                    self.skip = self.skip - 1;
                });
            });
        });
    };

    Paging.prototype.isEmpty = function () {
        return this.isSynchronized && this.items.length == 0;
    }

    return Paging;
} ]);

app.factory("MultiPaging", ["Paging", function (Paging) {
    function MultiPaging(options) {
        this.dico = [];
        this.options = options
    };

    MultiPaging.prototype.get = function (key) {
        var provider = this.dico[key];
        if (provider) return provider;

        var options = angular.copy(this.options);
        options.request.url += "/" + key; /* change url */

        provider = new Paging(options);
        this.dico[key] = provider;
        return provider;
    }

    return MultiPaging;
} ]);

app.factory("AppSingleData", ["$q", "$http", "$token", "$timeout", "$canceler", function ($q, $http, $token, $timeout, $canceler) {
    function AppSingleData(options) {
        this.data = null;
        this.store = options.store;
        this.requestOptions = options.request;
        this.isSynchronized = this.isReady = false;

        if(options.data){
            this.data = options.data;
            this.isSynchronized = this.isReady = true;
        }
    };

    AppSingleData.prototype.init = function () {
        var self = this;
        return $timeout(function () {
            if (self.isSynchronized) return self.data;
            return self.get();
        });
    };

    AppSingleData.prototype.get = function () { //first cache then http then update cache
        var self = this;

        return $timeout(function() {
            if (self.isReady) return;
            
            if (!self.store) {
                self.isReady = true;
                return;
            };

            return self.store.get(self.requestOptions.url).then(function (data) {
                //console.log(self.__proto__.constructor.name, "from cache", data);
                self.data = data;
                self.isReady = true;
            });
        }).then(function(){
            return $token.get().then(function (token) {
                return $http.get(self.requestOptions.url, { params: { token: token }, timeout: $canceler.create().promise }).then(function (res) {
                    self.data = res.data;
                    //console.log(self.__proto__.constructor.name, self.requestOptions.url, "=>", self.data);
                    self.isSynchronized = true;
                    $timeout(function(){
                        self.saveToStore();
                    });
                    return self.data;
                });
            });
        });
    };

    AppSingleData.prototype.saveToStore = function () { 
        var self = this;
        if(self.data && self.store) self.store.save(self.data, self.requestOptions.url);
    }

    return AppSingleData;
} ]);

app.factory("MultiData", ["AppSingleData", function (AppSingleData) {
    function MultiData(options) {
        this.dico = [];
        this.options = options || {};
        this.options.persistence = this.options.persistence || true;
    };

    MultiData.prototype.get = function (key) {
        var self = this;
        var provider = self.dico[key];
        if (provider) return provider;

        provider = self.createProvider(key);
        if(self.options.persistence) self.dico[key] = provider;

        return provider;
    };

    MultiData.prototype.createProvider = function (key) {
        var self = this;
        var Instance = self.options.type;

        var options = angular.copy(self.options);
        delete options.persistence; //not needed
        delete options.type; //not needed
        if(options.request)
            options.request.url += "/" + key; /* override url */

        return new Instance(options);
    };


    return MultiData;
} ]);
/********************************************************/



/***********************************************************/

app.factory("AlertPaging", ["Paging", "$location", "$rootScope", function (Paging, $location, $rootScope) {
    function AlertPaging(options) {
        Paging.call(this, options);
        var self = this;
        $rootScope.$on("/api/alert/save",function(){
            self.isSynchronized = false;
        })
    };

    AlertPaging.prototype = Object.create(Paging.prototype);
    AlertPaging.prototype.constructor = AlertPaging;

    AlertPaging.prototype.select = function(alert){
        alert.read = true;
        $rootScope.$broadcast("/api/alert/read", {id: alert._id});
        $location.path("#alert/" + alert._id);
    };

    return AlertPaging;
} ]);

app.factory("SubscriptionPaging", ["Paging", "$location", "$rootScope", function (Paging, $location, $rootScope) {
    function SubscriptionPaging(options) {
        Paging.call(this, options);
        var self = this;
        $rootScope.$on("/api/shop/subscribe/user",function(){
            self.isSynchronized = false;
        });
        $rootScope.$on("/api/shop/unsubscribe/user",function(){
            self.isSynchronized = false;
        });
        $rootScope.$on("/api/shop/subscribe/shop",function(){
            self.isSynchronized = false;
        });
        $rootScope.$on("/api/shop/unsubscribe/shop",function(){
            self.isSynchronized = false;
        });
    };

    SubscriptionPaging.prototype = Object.create(Paging.prototype);
    SubscriptionPaging.prototype.constructor = SubscriptionPaging;

    return SubscriptionPaging;
} ]);

/********************************************************************/
app.factory("appHome", ["AlertPaging", "homeStore", "Alert", function (AlertPaging, homeStore, Alert) {

    return new AlertPaging({
        request: {
            url: "/api/alert/near/user"
        },
        store: homeStore,
        groupBy: "data.dates.creation | date:'dd/MM/yyyy'",
        remove: {
            url: "/api/alert/user/delete",
            request: {
                key: "alertId",
                value: "item._id"
            }
        },
        singleApp: {
            type: Alert,
            options: {}
        }
    });
} ]);

app.factory("appNews", ["AlertPaging", "newsStore", "Alert", function (AlertPaging, newsStore, Alert) {
    return new AlertPaging({
        request: {
            url: "/api/alert/subscriber/user"
        },
        store: newsStore,
        groupBy: "data.dates.creation | date:'dd/MM/yyyy'",
        remove: {
            url: "/api/alert/user/delete",
            request: {
                key: "alertId",
                value: "item._id"
            }
        },
        singleApp: {
            type: Alert,
            options: {}
        }
    });
} ]);

app.factory("appLike", ["AlertPaging", "likeStore", "Alert", function (AlertPaging, likeStore, Alert) {
    return new AlertPaging({
        request: {
            url: "/api/alert/like/user"
        },
        store: likeStore,
        groupBy: "data.dates.creation | date:'dd/MM/yyyy'",
        remove: {
            url: "/api/alert/user/delete",
            request: {
                key: "alertId",
                value: "item._id"
            }
        },
        singleApp: {
            type: Alert,
            options: {}
        }
    });
} ]);

app.factory("appShop", ["MultiData", "shopStore", "Shop", function (MultiData, shopStore, Shop) {
    return new MultiData({
        request: {
            url: "/api/shop" /* key is append */
        },
        store: shopStore,
        type: Shop
    });
} ]);

app.factory("appShopAlerts", ["MultiPaging", "Alert", function (MultiPaging, Alert) { /*no store */
    return new MultiPaging({
        request: {
            url: "/api/alert/shop" /* key is append */
        },
        groupBy: "data.dates.creation | date:'dd/MM/yyyy'",
        remove: {
            url: "/api/alert/user/delete",
            request: {
                key: "alertId",
                value: "item._id"
            }
        },
        singleApp: {
            type: Alert,
            options: {}
        }
    });
} ]);

app.factory("appAccount", ["User", "accountStore", "connect", function (User, accountStore, connect) {
    
    function AppAccount() {
        User.call(this, {
            request: {
                url: "/api/user/full"
            },
            store: accountStore
        });
    };

    AppAccount.prototype = Object.create(User.prototype);
    AppAccount.prototype.constructor = AppAccount;

    AppAccount.prototype.disconnect = function () {
        return connect.disconnect();
    }

    return new AppAccount();
} ]);

app.factory("appSubscription", ["SubscriptionPaging", "subscriptionStore", "Shop", function (SubscriptionPaging, subscriptionStore, Shop) {
    return new SubscriptionPaging({
        request: {
            url: "/api/user/subscriptions"
        },
        store: subscriptionStore,
        groupBy: "data.login | firstLetter",
        remove: {
            url: "/api/shop/unsubscribe/user",
            request: {
                key: "shopId",
                value: "item._id"
            }
        },
        singleApp: {
            type: Shop,
            options: {}
        }
    });
} ]);

app.factory("appAlert", ["Alert", "MultiData", "alertStore", function (Alert, MultiData, alertStore) {
    return new MultiData({
        request: {
            url: "/api/alert" /* key is append */
        },
        store: alertStore,
        type: Alert 
    });
} ]);


app.factory("Search", ["Shop", "Alert", "User", "$location", "$http", "$canceler", "$rootScope", "$exception", function (Shop, Alert, User, $location, $http, $canceler, $rootScope, $exception) {

    function Search() {
        this.pagingUrl = "/api/search";
        this.researchUrl = "/api/research";
        this.researchPostUrl = "/api/research/call";
        this.value = null;
        this.items = [];
        this.skip = 0;
    };

    Search.prototype.goto = function (path, field) {
        if(!this.value) return;
        var id = $rootScope.$eval(field, this.value);
        $location.path("/" + path + "/" + id);
    };

    Search.prototype.research = function (searchText) {
        var self = this;
        return $http.get(self.researchUrl, { params: { q: searchText, limit: 10}, timeout: $canceler.create().promise }).then(function (res) {
            return res.data;
        }).catch(function (error) {
            $exception.show(error);
        });
    };

    Search.prototype.init = function (searchText) {
        var self = this;
        self.items = [];
        self.skip = 0;
        if(!searchText) self.value = null;
        else {
            self.value = {value: searchText};
            return $http.post(self.researchPostUrl, { q: searchText}, { timeout: $canceler.create().promise }).then(function(){
                return self.paging();
            }).catch(function (error) {
                $exception.show(error);
            });
        }
    };

    Search.prototype.paging = function () {
        var self = this;
        return $http.get(self.url, { params: { q: self.value.value, skip: self.skip, limit: 3}, timeout: $canceler.create().promise }).then(function (res) {
            
            res.data.forEach(function(item){
                var object = self.createProvider(item);
                self.items.push(object);
            });

            self.skip = self.items.length;
        }).catch(function (error) {
            $exception.show(error);
        });
    };

    Search.prototype.createProvider = function (data) {
        if(data.type == "alert") return new Alert({data: data.obj});
        if(data.type == "shop") return new Shop({data: data.obj});
        if(data.type == "user") return new User({data: data.obj});
    };

    return Search;
} ]);

app.factory("appSearch", ["Search", function (Search) {
    return new Search();
} ]);



